13.3 Logistische Regression mit Scikit-Learn

13.3 Logistische Regression mit Scikit-Learn#

Lernziele#

Lernziele

  • Sie können ein logistisches Regressionsmodell mit Scikit-Learn trainieren.

LogisticRegression#

Scikit-Learn bietet ein logistisches Regressionsmodell an, bei dem verschiedene Gradientenverfahren im Hintergrund die Gewichte bestimmen, die zu einer minimalen mittleren Kostenfunktion führen. Die Dokumentation zu dem logistischen Regressionsmodell findet sich hier: scikit-learn.org → LogisticRegression.

Wir wenden nun das Scikit-Learn-Modell auf unser Beispiel der binären Klassifikation “Ligazugehörigkeit abhängig vom Marktwert” deutscher Fußballvereine an. Dazu wiederholen laden wir die Daten und filtern zunächst nach Vereinen der 2. Bundesliga oder der 3. Liga.

# import all data
import pandas as pd
data_raw = pd.read_csv('data/20220801_Marktwert_Bundesliga.csv', skiprows=5, header=0, index_col=0)

# filter wrt 2. Bundesliga and 3. Liga
data = data_raw[ data_raw['Ligazugehörigkeit'] != 'Bundesliga' ]

# print all data samples
data.head(38)
Ligazugehörigkeit Wert Kadergröße
Verein
Hamburger SV 2. Bundesliga 34.85 28
Arminia Bielefeld 2. Bundesliga 31.10 26
SpVgg Greuther Fürth 2. Bundesliga 24.55 27
FC St. Pauli 2. Bundesliga 23.50 28
Fortuna Düsseldorf 2. Bundesliga 19.55 23
Hannover 96 2. Bundesliga 22.60 28
1.FC Nürnberg 2. Bundesliga 23.25 27
SV Darmstadt 98 2. Bundesliga 18.93 26
Karlsruher SC 2. Bundesliga 16.95 30
1.FC Heidenheim 1846 2. Bundesliga 16.65 27
Holstein Kiel 2. Bundesliga 16.38 30
SC Paderborn 07 2. Bundesliga 16.38 27
SV Sandhausen 2. Bundesliga 13.15 25
SSV Jahn Regensburg 2. Bundesliga 12.10 25
FC Hansa Rostock 2. Bundesliga 13.00 28
1.FC Kaiserslautern 2. Bundesliga 9.45 28
1.FC Magdeburg 2. Bundesliga 11.10 31
Eintracht Braunschweig 2. Bundesliga 8.83 26
Borussia Dortmund II 3. Liga 13.05 31
SG Dynamo Dresden 3. Liga 9.18 31
FC Ingolstadt 04 3. Liga 8.43 27
SC Freiburg II 3. Liga 6.23 31
FC Erzgebirge Aue 3. Liga 7.53 30
TSV 1860 München 3. Liga 6.65 30
SV Waldhof Mannheim 3. Liga 5.73 23
MSV Duisburg 3. Liga 5.55 28
FC Viktoria Köln 3. Liga 6.60 32
1. FC Saarbrücken 3. Liga 6.15 26
VfL Osnabrück 3. Liga 5.60 27
SV Wehen Wiesbaden 3. Liga 5.53 25
Rot-Weiss Essen 3. Liga 4.68 29
SV 07 Elversberg 3. Liga 4.05 26
SC Verl 3. Liga 3.68 28
Hallescher FC 3. Liga 3.95 24
SV Meppen 3. Liga 3.93 26
FSV Zwickau 3. Liga 3.80 24
SpVgg Bayreuth 3. Liga 3.25 30
VfB Oldenburg 3. Liga 3.06 27

Als nächstes formulieren wir das Klassifikationsproblem: Gegeben ist ein Verein mit seinem Marktwert. Spielt der Verein in der 2. Bundesliga?

Die Klasse 2. Bundesliga wird in den Daten als 1 codiert, da der ML-Algorithmus nur mit numerischen Daten arbeiten kann. Den String 3. Liga ersetzen wir in den Trainingsdaten durch eine 0.

# encode categorical data
data.replace('2. Bundesliga', 1, inplace=True)
data.replace('3. Liga', 0, inplace=True)
/var/folders/hd/xdjzhymn4x393gh7v6ws4v880000gn/T/ipykernel_2838/2351413053.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  data.replace('2. Bundesliga', 1, inplace=True)
/var/folders/hd/xdjzhymn4x393gh7v6ws4v880000gn/T/ipykernel_2838/2351413053.py:3: FutureWarning: Downcasting behavior in `replace` is deprecated and will be removed in a future version. To retain the old behavior, explicitly call `result.infer_objects(copy=False)`. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
  data.replace('3. Liga', 0, inplace=True)
/var/folders/hd/xdjzhymn4x393gh7v6ws4v880000gn/T/ipykernel_2838/2351413053.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  data.replace('3. Liga', 0, inplace=True)

Jetzt können wir das logistische Regressionsmodell laden:

from sklearn.linear_model import LogisticRegression

logistic_regression = LogisticRegression()

Die Daten werden jetzt in Matrizen gepackt und in Trainings- und Testdaten unterteilt:

import numpy as np
from sklearn.model_selection import train_test_split

X = data[['Wert']]
y = data['Ligazugehörigkeit']

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

Danach können wir das logistische Regressionsmodell trainieren:

logistic_regression.fit(X_train, y_train)
LogisticRegression()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Und dann als nächstes beurteilen, wie viele der Testdaten korrekt klassfiziert werden.

logistic_regression.score(X_test, y_test)
0.9

90 % der Testdaten werden korrekt klassifiziert. Mit einer anderen Aufteilung in Trainings- und Testdaten können wir auch höhere Erkennungsraten erzielen. Beispielsweise führt ein Split mit random_state=1 zu einer 100 % genauen Klassifikation der Testdaten.

Als nächstes lassen wir Python alle Daten zusammen mit der Wahrscheinlichkeitsfunktion visualisieren.

# extrahiere die Gewichte des logistischen Regressionsmodells
gewichte = np.concatenate((logistic_regression.intercept_, logistic_regression.coef_[:,0]))
print(f'Gewichte: {gewichte}')

# definiere Wahrschinelichkeitsfunktion
def wahrscheinlichkeitsfunktion(x, w):
    z = w[0] + x * w[1]
    return 1/(1+np.exp(-z))

# stelle Wartetabelle der Wahrscheinlichkeitsfunktion auf
x = np.linspace(0, 35)
sigma_z = wahrscheinlichkeitsfunktion(x, gewichte)

# trenne Daten gemäß Ligazugehörigkeit
data_zweite_liga = data[data['Ligazugehörigkeit'] == 1]
data_dritte_liga = data[data['Ligazugehörigkeit'] == 0]
Gewichte: [-6.73251103  0.63014533]
import plotly.express as px
import plotly.graph_objects as go

fig3 = px.scatter(data_dritte_liga, x = 'Wert', y = 'Ligazugehörigkeit')
fig2 = px.scatter(data_zweite_liga, x = 'Wert', y = 'Ligazugehörigkeit')
fig_model = px.line(x = x, y = sigma_z)

fig = go.Figure(fig_model.data + fig2.data + fig3.data)
fig.update_layout(title='Klassifikation 2. Bundesliga / 3. Liga',
                  xaxis_title='Marktwert',
                  yaxis_title='Ligazugehörigkeit')
fig.show()

Aus der Visualisierung der Wahrscheinlichkeitsfunktion können wir grob abschätzen, bei welchem Marktwert ein Verein als Zweit- oder Drittligist klassifiziert wird. Die Wahrscheinlichkeitsfunktion schneidet die 50 % Grenzlinie ungefähr bei einem Marktwert von 11 Mio. Euro. Etwas genauer können wir diese Grenze durch das Kommando fsolve aus dem Scipy-Modul bestimmen lassen:

from scipy.optimize import fsolve

x_grenze =  fsolve(lambda x: wahrscheinlichkeitsfunktion(x, gewichte) - 0.5, 11.0)
print('Grenze des Marktwertes: {:.2f} Mio. Euro'.format(x_grenze[0]))
Grenze des Marktwertes: 10.68 Mio. Euro

Zusammenfassung#

In diesem Abschnitt haben wir an einem Beispiel gesehen, wie das logistische Regressionsmodell von Scikit-Learn trainiert und bewertet wird.